<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>

                    <h4>使用dict和set</h4>
                    <div class="x-wiki-info"><span>4078次阅读</span></div>
                    <hr style="border-top-color:#ccc" />
                    <div class="x-wiki-content x-content"><h3 id="dict">dict</h3>
<p>Python内置了字典：dict的支持，dict全称dictionary，在其他语言中也称为map，使用键-值（key-value）存储，具有极快的查找速度。</p>
<p>举个例子，假设要根据同学的名字查找对应的成绩，如果用list实现，需要两个list：</p>
<pre><code>names = [&#39;Michael&#39;, &#39;Bob&#39;, &#39;Tracy&#39;]
scores = [95, 75, 85]
</code></pre><p>给定一个名字，要查找对应的成绩，就先要在names中找到对应的位置，再从scores取出对应的成绩，list越长，耗时越长。</p>
<p>如果用dict实现，只需要一个“名字”-“成绩”的对照表，直接根据名字查找成绩，无论这个表有多大，查找速度都不会变慢。用Python写一个dict如下：</p>
<pre><code>&gt;&gt;&gt; d = {&#39;Michael&#39;: 95, &#39;Bob&#39;: 75, &#39;Tracy&#39;: 85}
&gt;&gt;&gt; d[&#39;Michael&#39;]
95
</code></pre><p>为什么dict查找速度这么快？因为dict的实现原理和查字典是一样的。假设字典包含了1万个汉字，我们要查某一个字，一个办法是把字典从第一页往后翻，直到找到我们想要的字为止，这种方法就是在list中查找元素的方法，list越大，查找越慢。</p>
<p>第二种方法是先在字典的索引表里（比如部首表）查这个字对应的页码，然后直接翻到该页，找到这个字，无论找哪个字，这种查找速度都非常快，不会随着字典大小的增加而变慢。</p>
<p>dict就是第二种实现方式，给定一个名字，比如<code>&#39;Michael&#39;</code>，dict在内部就可以直接计算出<code>Michael</code>对应的存放成绩的“页码”，也就是<code>95</code>这个数字存放的内存地址，直接取出来，所以速度非常快。</p>
<p>你可以猜到，这种key-value存储方式，在放进去的时候，必须根据key算出value的存放位置，这样，取的时候才能根据key直接拿到value。</p>
<p>把数据放入dict的方法，除了初始化时指定外，还可以通过key放入：</p>
<pre><code>&gt;&gt;&gt; d[&#39;Adam&#39;] = 67
&gt;&gt;&gt; d[&#39;Adam&#39;]
67
</code></pre><p>由于一个key只能对应一个value，所以，多次对一个key放入value，后面的值会把前面的值冲掉：</p>
<pre><code>&gt;&gt;&gt; d[&#39;Jack&#39;] = 90
&gt;&gt;&gt; d[&#39;Jack&#39;]
90
&gt;&gt;&gt; d[&#39;Jack&#39;] = 88
&gt;&gt;&gt; d[&#39;Jack&#39;]
88
</code></pre><p>如果key不存在，dict就会报错：</p>
<pre><code>&gt;&gt;&gt; d[&#39;Thomas&#39;]
Traceback (most recent call last):
  File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;
KeyError: &#39;Thomas&#39;
</code></pre><p>要避免key不存在的错误，有两种办法，一是通过<code>in</code>判断key是否存在：</p>
<pre><code>&gt;&gt;&gt; &#39;Thomas&#39; in d
False
</code></pre><p>二是通过dict提供的get方法，如果key不存在，可以返回None，或者自己指定的value：</p>
<pre><code>&gt;&gt;&gt; d.get(&#39;Thomas&#39;)
&gt;&gt;&gt; d.get(&#39;Thomas&#39;, -1)
-1
</code></pre><p>注意：返回None的时候Python的交互式命令行不显示结果。</p>
<p>要删除一个key，用<code>pop(key)</code>方法，对应的value也会从dict中删除：</p>
<pre><code>&gt;&gt;&gt; d.pop(&#39;Bob&#39;)
75
&gt;&gt;&gt; d
{&#39;Michael&#39;: 95, &#39;Tracy&#39;: 85}
</code></pre><p>请务必注意，dict内部存放的顺序和key放入的顺序是没有关系的。</p>
<p>和list比较，dict有以下几个特点：</p>
<ol>
<li>查找和插入的速度极快，不会随着key的增加而增加；</li>
<li>需要占用大量的内存，内存浪费多。</li>
</ol>
<p>而list相反：</p>
<ol>
<li>查找和插入的时间随着元素的增加而增加；</li>
<li>占用空间小，浪费内存很少。</li>
</ol>
<p>所以，dict是用空间来换取时间的一种方法。</p>
<p>dict可以用在需要高速查找的很多地方，在Python代码中几乎无处不在，正确使用dict非常重要，需要牢记的第一条就是dict的key必须是<strong>不可变对象</strong>。</p>
<p>这是因为dict根据key来计算value的存储位置，如果每次计算相同的key得出的结果不同，那dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法（Hash）。</p>
<p>要保证hash的正确性，作为key的对象就不能变。在Python中，字符串、整数等都是不可变的，因此，可以放心地作为key。而list是可变的，就不能作为key：</p>
<pre><code>&gt;&gt;&gt; key = [1, 2, 3]
&gt;&gt;&gt; d[key] = &#39;a list&#39;
Traceback (most recent call last):
  File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;
TypeError: unhashable type: &#39;list&#39;
</code></pre><h3 id="set">set</h3>
<p>set和dict类似，也是一组key的集合，但不存储value。由于key不能重复，所以，在set中，没有重复的key。</p>
<p>要创建一个set，需要提供一个list作为输入集合：</p>
<pre><code>&gt;&gt;&gt; s = set([1, 2, 3])
&gt;&gt;&gt; s
set([1, 2, 3])
</code></pre><p>注意，传入的参数<code>[1, 2, 3]</code>是一个list，而显示的<code>set([1, 2, 3])</code>只是告诉你这个set内部有1，2，3这3个元素，显示的[]不表示这是一个list。</p>
<p>重复元素在set中自动被过滤：</p>
<pre><code>&gt;&gt;&gt; s = set([1, 1, 2, 2, 3, 3])
&gt;&gt;&gt; s
set([1, 2, 3])
</code></pre><p>通过<code>add(key)</code>方法可以添加元素到set中，可以重复添加，但不会有效果：</p>
<pre><code>&gt;&gt;&gt; s.add(4)
&gt;&gt;&gt; s
set([1, 2, 3, 4])
&gt;&gt;&gt; s.add(4)
&gt;&gt;&gt; s
set([1, 2, 3, 4])
</code></pre><p>通过<code>remove(key)</code>方法可以删除元素：</p>
<pre><code>&gt;&gt;&gt; s.remove(4)
&gt;&gt;&gt; s
set([1, 2, 3])
</code></pre><p>set可以看成数学意义上的无序和无重复元素的集合，因此，两个set可以做数学意义上的交集、并集等操作：</p>
<pre><code>&gt;&gt;&gt; s1 = set([1, 2, 3])
&gt;&gt;&gt; s2 = set([2, 3, 4])
&gt;&gt;&gt; s1 &amp; s2
set([2, 3])
&gt;&gt;&gt; s1 | s2
set([1, 2, 3, 4])
</code></pre><p>set和dict的唯一区别仅在于没有存储对应的value，但是，set的原理和dict一样，所以，同样不可以放入可变对象，因为无法判断两个可变对象是否相等，也就无法保证set内部“不会有重复元素”。试试把list放入set，看看是否会报错。</p>
<h3 id="-">再议不可变对象</h3>
<p>上面我们讲了，str是不变对象，而list是可变对象。</p>
<p>对于可变对象，比如list，对list进行操作，list内部的内容是会变化的，比如：</p>
<pre><code>&gt;&gt;&gt; a = [&#39;c&#39;, &#39;b&#39;, &#39;a&#39;]
&gt;&gt;&gt; a.sort()
&gt;&gt;&gt; a
[&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]
</code></pre><p>而对于不可变对象，比如str，对str进行操作呢：</p>
<pre><code>&gt;&gt;&gt; a = &#39;abc&#39;
&gt;&gt;&gt; a.replace(&#39;a&#39;, &#39;A&#39;)
&#39;Abc&#39;
&gt;&gt;&gt; a
&#39;abc&#39;
</code></pre><p>虽然字符串有个<code>replace()</code>方法，也确实变出了<code>&#39;Abc&#39;</code>，但变量<code>a</code>最后仍是<code>&#39;abc&#39;</code>，应该怎么理解呢？</p>
<p>我们先把代码改成下面这样：</p>
<pre><code>&gt;&gt;&gt; a = &#39;abc&#39;
&gt;&gt;&gt; b = a.replace(&#39;a&#39;, &#39;A&#39;)
&gt;&gt;&gt; b
&#39;Abc&#39;
&gt;&gt;&gt; a
&#39;abc&#39;
</code></pre><p>要始终牢记的是，<code>a</code>是变量，而<code>&#39;abc&#39;</code>才是字符串对象！有些时候，我们经常说，对象<code>a</code>的内容是<code>&#39;abc&#39;</code>，但其实是指，<code>a</code>本身是一个变量，它指向的对象的内容才是<code>&#39;abc&#39;</code>：</p>
<p><img src="http://www.liaoxuefeng.com/files/attachments/001389580505217f87b492b060b4b0ea60c8e5e70a1b53c000/0" alt="a-to-str"></p>
<p>当我们调用<code>a.replace(&#39;a&#39;, &#39;A&#39;)</code>时，实际上调用方法<code>replace</code>是作用在字符串对象<code>&#39;abc&#39;</code>上的，而这个方法虽然名字叫<code>replace</code>，但却没有改变字符串<code>&#39;abc&#39;</code>的内容。相反，<code>replace</code>方法创建了一个新字符串<code>&#39;Abc&#39;</code>并返回，如果我们用变量<code>b</code>指向该新字符串，就容易理解了，变量<code>a</code>仍指向原有的字符串<code>&#39;abc&#39;</code>，但变量<code>b</code>却指向新字符串<code>&#39;Abc&#39;</code>了：</p>
<p><img src="http://www.liaoxuefeng.com/files/attachments/001389580620829061e426d429640ddb1d17174a82a7244000/0" alt="a-b-to-2-strs"></p>
<p>所以，对于不变对象来说，调用对象自身的任意方法，也不会改变该对象自身的内容。相反，这些方法会创建新的对象并返回，这样，就保证了不可变对象本身永远是不可变的。</p>
<h3 id="-">小结</h3>
<p>使用key-value存储结构的dict在Python中非常有用，选择不可变对象作为key很重要，最常用的key是字符串。</p>
<p>tuple虽然是不变对象，但试试把<code>(1, 2, 3)</code>和<code>(1, [2, 3])</code>放入dict或set中，并解释结果。</p>
</div>

                    <hr style="border-top-color:#ccc" />

                    